package org.test4j.mock.processor.filer.file;

import com.squareup.javapoet.ClassName;

import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import java.util.*;

import static org.test4j.mock.faking.modifier.FakeTransformer.notMockType;
import static org.test4j.mock.faking.util.AsmConstant.Method_CL_INIT;
import static org.test4j.mock.faking.util.TypeUtility.isSynthetic;
import static org.test4j.mock.processor.filer.ExecutableHelper.*;

public class MockTypeFiler extends MockUpFiler {
    private TypeElement typeElement;

    public MockTypeFiler(TypeElement typeElement) {
        super(ClassName.get(typeElement));
        this.typeElement = typeElement;
        this.parserSuperClass();
        this.parseMethodIds();
        this.parseAbstractMethodIds();
    }

    private void parseAbstractMethodIds() {
        Queue<TypeMirror> queue = new LinkedList<>();
        typeElement.getInterfaces().forEach(queue::offer);
        while (!queue.isEmpty()) {
            TypeMirror type = queue.poll();
            if (type == null || !(type instanceof DeclaredType)) {
                continue;
            }
            Element element = ((DeclaredType) type).asElement();
            if (!(element instanceof TypeElement)) {
                continue;
            }
            this.parseTypeMethod((TypeElement) element);
            ((TypeElement) element).getInterfaces().forEach(queue::offer);
        }
    }

    protected void parserSuperClass() {
        TypeMirror superTypeMirror = typeElement.getSuperclass();
        if (!(superTypeMirror instanceof DeclaredType)) {
            return;
        }
        Element superType = ((DeclaredType) superTypeMirror).asElement();
        String fullName = superType.toString();
        if (!notMockType(fullName)) {
            super.superClass = fullName;
        }
    }

    protected void parseMethodIds() {
        this.parseTypeMethod(typeElement);
    }

    private void parseTypeMethod(TypeElement type) {
        List<? extends Element> elements = type.getEnclosedElements();
        Map<String, String> classVars = getVarDesc(type.getTypeParameters(), new HashMap<>());
        for (Element element : elements) {
            if (!(element instanceof ExecutableElement)) {
                continue;
            }
            ExecutableElement executable = (ExecutableElement) element;
            String name = executable.getSimpleName().toString();
            int access = getAccess(executable);
            if (Method_CL_INIT.equals(name) || isSynthetic(access)) {
                continue;
            }
            String paraDesc = getParaDesc(executable, classVars);
            this.addMethodId(access, name, paraDesc);
        }
    }
}